package cruncher;

import ilog.concert.IloCopyManager;
import ilog.concert.IloCopyable;
import ilog.concert.IloException;
import ilog.concert.IloLPMatrix;
import ilog.concert.IloNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloNumVarArray;
import ilog.concert.IloNumVarType;
import ilog.concert.IloCopyManager.Check;
import ilog.concert.IloRange;
import ilog.concert.cppimpl.IloConstraint;
import ilog.concert.cppimpl.IloIntVar;
import ilog.cplex.IloCplex;
import ilog.cplex.IloCplex.CplexStatus;
import ilog.cplex.IloCplex.Status;
import ilog.cplex.IloCplex.MIPStartEffort;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Random;

import utils.Logger;
import utils.Timer;

public class NaiveCruncher {
	IloCplex currentModel = null; //this model is modified and solved
	private String modelFilename;
	private int timeLimitSec = 0; //uninitialized
	private PriorityQueue<Solution> solutionPool = new PriorityQueue<Solution>();
	private boolean displayCPLEXlog = true; 
	public NaiveCruncher() {
		 try {
			 currentModel = new IloCplex();
			 if (!displayCPLEXlog)
			 {
				 currentModel.setOut(null);
			 }
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	
	
	public void readfile(String file) {
		try {
			modelFilename = file;
			currentModel.importModel(modelFilename);
			
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void readMIPStartFile(String file)
	{
		try {
			currentModel.readMIPStart(file);
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public Solution getFeasibleSolution(int numSolutions)
	{
		//look for the first feasible solution only
		Solution res = null;
		try {
			int oldValue = currentModel.getParam(IloCplex.IntParam.IntSolLim);
			currentModel.setParam(IloCplex.IntParam.IntSolLim,numSolutions);
			res = solve();
			currentModel.setParam(IloCplex.IntParam.IntSolLim,oldValue);
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return res;
	}
	
	public Solution solve() {

		try {

			//currentModel.exportModel(String.format("logs\\solveModel.%d.mps", System.currentTimeMillis()));
			boolean solved = currentModel.solve();
			//System.out.println(currentModel.getStatus());
			if (solved)
			{
			   currentModel.writeMIPStart("solution.mst");
			   Solution sol = new Solution(currentModel.getObjValue());
		       IloLPMatrix lp = (IloLPMatrix)currentModel.LPMatrixIterator().next();
		       IloNumVar[] vars = lp.getNumVars();
		       
		       double[] vals = currentModel.getValues(lp);
		       for (int j = 0; j < vals.length; ++j)
		       {
		    	  IloNumVarType type = vars[j].getType();
		    	  boolean isDecisionVar = false;
		    	  if (type == IloNumVarType.Int ||
		    		  type == IloNumVarType.Bool)
		    	  {
		    		  isDecisionVar = true;
		    	  }
		    	  
		    	  sol.add(new Variable(vars[j].getName(), 
		    			  			   isDecisionVar ? Utils.TYPE_INTEGER : Utils.TYPE_FRACTIONAL, 
		    			  			   vals[j]));
		          //System.out.println("Variable " + vars[j] + ": Value = " + vals[j]);
		       }
		        Status stSolution = currentModel.getStatus();
		        CplexStatus stCplex = currentModel.getCplexStatus();
		        Logger.INFO(String.format("Status: Solution %s, Cplex: %s", stSolution,stCplex));
		       	if (stSolution == Status.Optimal && 
		       			(stCplex == CplexStatus.Optimal || stCplex == CplexStatus.OptimalTol))
		       	{
		       		sol.setOptimal(true);
		       	}
				return sol;
			}
			
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public Solution localSearch(int runningTime){
		int iterTime = 3*60;
		double fixingFraction = 0.8;
		long moreTime = runningTime * 1000; //msec
		Timer timer = new Timer();
		int iterations = 0;
		
		setTimeLimitSec((int)(moreTime/1000));
		Logger.TRACE(String.format("Looking for a feasible solution..."));
		timer.Start();
		Solution current = this.getFeasibleSolution(3);
		moreTime -= timer.Stop();
		
		if (current == null)
		{
			Logger.INFO(String.format("Didn't find a feasible solution in %d seconds", runningTime));
			return null;
		}
		
		Logger.INFO(String.format("Found solution with value %f",current.getObjValue()));
		Logger.INFO(String.format("Time left %d seconds",(moreTime/1000)));
		
		solutionPool.offer(current);
		this.setTimeLimitSec(iterTime);
		
		//fix and improve
		while (moreTime >= 0){

			current = solutionPool.peek(); //get best objective solution
			this.reset(); //reset the model to original (root) w/o fixings

			try
				{
					Logger.INFO(String.format("Starting search iteration: %d ",++iterations));
					
					timer.Start();
					FixingStrategy fix = new FixingStrategy(fixingFraction);
					HashSet<Variable> fixedVars = fix.getFixedSet(current);
					this.fixVariables(fixedVars);
					Logger.INFO(String.format("Modified model: fixed %d variables",fixedVars.size()));
					Logger.INFO(String.format("Adding start solution with value %f",current.getObjValue()));
					Logger.TRACE("Solving...");
					this.addMIPStart(current);
					Solution next = solve();
					
					if (next != null)
					{
						Logger.INFO(String.format("Found solution with value %f",next.getObjValue()));
						if (!current.equals(next))
						{
							solutionPool.offer(next);
						}
						
						if (next.isOptimal())
						{
							Logger.TRACE(String.format("Found optimal solution with value %f, restarting search...",next.getObjValue()));
							//restart = true;
							//reduce the fixing fraction if there's no improvement
							if (next.getObjValue() >= current.getObjValue())
							{
								fixingFraction = Math.max(0, fixingFraction - 0.05);
							}
						}
						
						//add solution to the pool
						current = next;
					}

					moreTime -= timer.Stop();
					Logger.INFO(String.format("Time left %d seconds",moreTime/1000));
					if (moreTime <= 0)
					{
						break;
					}
					else if (moreTime/1000 < iterTime)
					{
						this.setTimeLimitSec((int)moreTime/1000);
					}
				}
				catch(Exception e)
				{
					e.printStackTrace();
				}
				
		} //while moreTime
		return solutionPool.peek();
	}

	
	private void reset() {
		currentModel.end();
		try {
			currentModel = new IloCplex();
			 if (!displayCPLEXlog)
			 {
				 currentModel.setOut(null);
			 }
			currentModel.importModel(this.modelFilename);

			if (timeLimitSec > 0) //user changed the value
				setTimeLimitSec(timeLimitSec);
		} catch (IloException e) {
			e.printStackTrace();
		}
	}



	public void setTimeLimitSec(int timeoutSec) {
		try {
			timeLimitSec = timeoutSec;
			currentModel.setParam(IloCplex.DoubleParam.TiLim, timeLimitSec);
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	//TODO make it efficient
	public void fixVariables(HashSet<Variable> fixedVars) {
	
	Logger.DEBUG("Fixing...");
	try {
		//release resources by old CPLEX native implementation
		//currentModel.exportModel("before.mps");
		reset();
		
		//find variable by name and set its bounds
		IloLPMatrix lp = (IloLPMatrix)currentModel.LPMatrixIterator().next();
	    IloNumVar[] vars = lp.getNumVars();
	    
	    List<IloRange> newRows = new LinkedList<IloRange>();
	    
	    HashMap<String, Variable> vMap = new HashMap<String, Variable>(); 
		for (Variable v : fixedVars)
		{
			//System.out.println("Fixing " + v.getName()); 
			if (!v.isInteger())
				throw new Exception("variable " + v.getName() + "is not decision, can't be fixed");
			vMap.put(v.getName(), v);
		}

		    for (IloNumVar numVar : vars)
		    {
		    	String name = numVar.getName();
		    	if (vMap.containsKey(name))
		    	{
		    		IloRange range = currentModel.eq(numVar, vMap.get(name).getValue());
		    		newRows.add(range);
		    		
		    	}
		    }

	    
		//add to the model
		IloRange[] aRange = new IloRange[0];
		aRange = newRows.toArray(aRange);
		//add rows to the Cplex, adding to LPMatrix isn't implemented
	    currentModel.add(aRange);
		currentModel.exportModel("after.mps");
		
		}catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	Logger.DEBUG("Done Fixing...");
	}


	private IloNumVar[] getSolutionVars(Solution sol) {
		IloNumVar[] resVars = null;
		try {
		//find variable by name and set its bounds
		IloLPMatrix lp = (IloLPMatrix)currentModel.LPMatrixIterator().next();
	    IloNumVar[] vars;

			vars = lp.getNumVars();

	    
	    HashMap<String, IloNumVar> iloNumVarMap = new HashMap<String, IloNumVar>(); 

		for (IloNumVar numVar : vars)
		   {
		     String name = numVar.getName();
		     iloNumVarMap.put(name, numVar);
		   }
		    	
		resVars = new IloNumVar[sol.size()];
			
		int i = 0;
		for (Variable v : sol)
		{
			resVars[i] = iloNumVarMap.get(v.getName());
			++i;
		}
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return resVars;

	}

	private double[] getSolutionValues(Solution sol) {
		double[] resVals = null;

		resVals = new double[sol.size()];
			
		int i = 0;
		for (Variable v : sol)
			{
				resVals[i] = v.getValue();
				++i;
			}

		return resVals;
	}
	
	public Solution solveWithStartSolution(Solution start) {
		IloNumVar[] vars = getSolutionVars(start);
		double[] values = getSolutionValues(start);
		
		Solution solution = null;
		try {
			currentModel.addMIPStart(vars, values);
			solution = solve();
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return solution;
	}

	public void addMIPStart(Solution start) {
		IloNumVar[] vars = getSolutionVars(start);
		double[] values = getSolutionValues(start);
		
		try {
			currentModel.addMIPStart(vars, values);
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public Solution solveWithMIPStart() {
	
		Solution solution = null;
		try {
			currentModel.writeMIPStarts("mipstart.mst");
			//simulate changing the model
			reset();
			currentModel.readMIPStart("mipstart.mst");
			//currentModel.changeMIPStart(1, MIPStartEffort.Repair);
			int num = currentModel.getNMIPStarts();
			Logger.INFO("Number of MIPStarts is " + num);
			//currentModel.changeMIPStart(0,MIPStartEffort.CheckFeas);
			solution = solve();
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return solution;
	}
	
	public Solution solveWithPolish(int timeLimitSec,int branchAndBoundTimeSec, int solutions) {
		Solution sol = null;
		try {
			setTimeLimitSec(timeLimitSec);
			currentModel.setParam(IloCplex.DoubleParam.PolishAfterTime, branchAndBoundTimeSec);
			currentModel.setParam(IloCplex.IntParam.PolishAfterIntSol, solutions);
			sol = solve();
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return sol;
	}

	public void setPolish(int timeLimitSec,int branchAndBoundTimeSec, int solutions) {
		
		try {
			setTimeLimitSec(timeLimitSec);
			currentModel.setParam(IloCplex.DoubleParam.PolishAfterTime, branchAndBoundTimeSec);
			currentModel.setParam(IloCplex.IntParam.PolishAfterIntSol, solutions);
		
		} catch (IloException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public void cleanup() {
		currentModel.end();
		
	}
			
	protected void finalize() throws Throwable{
	    try{
	    	cleanup();
	    }
	    finally{
	      super.finalize();
	    }
	  }
}
